package com.beemans.common.ui.views.exposure

import android.graphics.Rect
import android.view.View
import android.view.ViewTreeObserver
import com.tiamosu.fly.base.action.HandlerAction

/**
 * @author tiamosu
 * @date 2022/3/20
 */
class ExposureHandler(private val view: View) : ViewTreeObserver.OnPreDrawListener,
    HandlerAction {
    private var isAttachedToWindow = false                  //添加到视图中的状态
    private var isWindowFocusChanged = true                 //视图获取到焦点的状态 ，默认为true，避免某些场景不被调用
    private var isVisibilityAggregated = true               //可见性的状态 ，默认为true，避免某些场景不被调用
    private var isExposure = false                          //当前是否处于曝光状态
    private var showRatio = 0f                              //曝光条件超过多大面积 0~1f
    private var timeLimit = 0L                              //曝光条件超过多久才算曝光，例如2秒(2000)

    private val rect by lazy { Rect() }                     //实时曝光面积
    private var exposureCallback: IExposureCallback? = null //曝光回调
    private var exposureRunnable: Runnable? = null          //执行曝光任务

    var isLocalVisible = false                              //视图处于屏幕可见
        private set

    /**
     * 添加到视图时添加OnPreDrawListener
     */
    fun onAttachedToWindow() {
        isAttachedToWindow = true
        view.viewTreeObserver.apply {
            if (!isAlive) return@apply
            addOnPreDrawListener(this@ExposureHandler)
        }
        tryExposure()
    }

    /**
     * 从视图中移除时去掉OnPreDrawListener
     * 尝试取消曝光
     */
    fun onDetachedFromWindow() {
        isAttachedToWindow = false
        view.viewTreeObserver.apply {
            if (!isAlive) return@apply
            removeOnPreDrawListener(this@ExposureHandler)
        }
        tryStopExposure()
    }

    /**
     * 视图焦点改变
     * 尝试取消曝光
     */
    fun onWindowFocusChanged(hasWindowFocus: Boolean) {
        isWindowFocusChanged = hasWindowFocus

        if (hasWindowFocus) {
            tryExposure()
        } else {
            tryStopExposure()
        }
    }

    /**
     * 可见性改变
     * 尝试取消曝光
     */
    fun onVisibilityAggregated(isVisible: Boolean) {
        isVisibilityAggregated = isVisible

        if (isVisible) {
            tryExposure()
        } else {
            tryStopExposure()
        }
    }

    /**
     * 视图预绘制
     * 当曝光面积达到条件是尝试曝光
     * 当视图面积不满足条件时尝试取消曝光
     */
    override fun onPreDraw(): Boolean {
        val visible = view.getLocalVisibleRect(rect) && view.isShown   //获取曝光面积和View的Visible
        if (!visible) {
            tryStopExposure()   //不达到曝光条件时尝试取消曝光
            return true
        }
        if (showRatio > 0) {   //存在曝光面积限制条件时
            if (kotlin.math.abs(rect.bottom - rect.top) > view.height * showRatio
                && kotlin.math.abs(rect.right - rect.left) > view.width * showRatio
            ) {
                tryExposure()   //达到曝光条件时尝试曝光
            } else {
                tryStopExposure()   //不达到曝光条件时尝试取消曝光
            }
        } else {
            tryExposure()   //达到曝光条件时尝试曝光
        }
        return true
    }

    /**
     * 曝光回调
     */
    fun setExposureCallback(callback: IExposureCallback) {
        exposureCallback = callback
    }

    /**
     * 设置曝光面积条件
     */
    fun setShowRatio(area: Float) {
        showRatio = area
    }

    /**
     * 设置曝光时间限制条件
     */
    fun setTimeLimit(index: Long) {
        timeLimit = index
    }

    /**
     * 尝试曝光
     */
    private fun tryExposure() {
        if (isAttachedToWindow && isWindowFocusChanged && isVisibilityAggregated && !isExposure) {
            isExposure = true
            exposureRunnable = Runnable {
                if (!isLocalVisible) {
                    isLocalVisible = true
                    exposureCallback?.invoke(true)
                }
            }
            postDelayed(exposureRunnable, timeLimit)
        }
    }

    /**
     * 尝试取消曝光
     */
    private fun tryStopExposure() {
        if (isExposure) {
            isExposure = false
            removeCallbacks(exposureRunnable)
            if (isLocalVisible) {
                isLocalVisible = false
                exposureCallback?.invoke(false)
            }
        }
    }
}
